﻿#region header

//  ***************************************************************************
// *                                LICENSE
//  ***************************************************************************
//  * "Haplous" .NET Simple 2D Game Framework
//  * 
//  * Copyright © Jonathan Evans 2014-2014
//  * 
//  * This program is free software; you can redistribute it and/or
//  * modify it under the terms of the GNU General Public License
//  * as published by the Free Software Foundation; either version 2
//  * of the License, or (at your option) any later version.
//  * 
//  * This program is distributed in the hope that it will be useful,
//  * but WITHOUT ANY WARRANTY; without even the implied warranty of
//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  * GNU General Public License for more details.
//  * 
//  * You should have received a copy of the GNU General Public License
//  * along with this program; if not, write to the Free Software
//  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//  * 
//  ***************************************************************************
//  * 
//  * Contact: opencollar@googlemail.com
//  * 
//  * Website: http://haplous.codeplex.com/
//  *
//  * NuGet:   http://www.nuget.org/packages/haplous/
//  *
//  * File:    Haplous/Haplous/GraphicBlockWrapper.cs
//  *
//  ***************************************************************************

#endregion

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Threading;
using System.Windows;
using System.Windows.Media;

using Haplous.Core;

using JetBrains.Annotations;

namespace Haplous.Presentation.ViewModel
{
    /// <summary>
    ///     A wrapper around element objects providing the model used for displaying it.
    /// </summary>
    [DebuggerDisplay("GraphicBlockWrapper: Name={Block.Name}")]
    internal class GraphicBlockWrapper : BlockWrapper
    {
        /// <summary>
        ///     A locked used to control access to the <See cref="_recalculationQueueing" /> flag.
        /// </summary>
        [NotNull]
        private readonly object _recalculationQueueLock = new object();

        /// <summary>
        ///     The image to display.
        /// </summary>
        [CanBeNull]
        private Image _image;

        /// <summary>
        ///     A flag indicating that a recalculation has been queued.
        /// </summary>
        private volatile bool _recalculationQueueing;

        /// <summary>
        ///     The way in which the image should be stretched
        /// </summary>
        private Stretch _stretch;

        /// <summary>
        ///     The tiling mode to apply to the image.
        /// </summary>
        private TileMode _tileMode;

        /// <summary>
        ///     The scaling to apply to the tiles.
        /// </summary>
        private Rect _viewport;

        /// <summary>
        ///     The way in which to scale the tiles.
        /// </summary>
        private BrushMappingMode _viewportUnits;

        /// <summary>
        ///     Initializes a new instance of the <see cref="GraphicBlockWrapper" /> class.
        /// </summary>
        /// <param name="viewModel">The view model to which this wrapper belongs.</param>
        /// <param name="block">The element being wrapped.</param>
        /// <param name="zOrder">The Z-order of this block.</param>
        [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public GraphicBlockWrapper([NotNull] GameAreaViewModel viewModel, [NotNull] IGraphicBlock block, int zOrder) : base(viewModel, block, zOrder)
        {
            RequestImageRecalculation();
        }

        /// <summary>
        ///     The block being wrapped.
        /// </summary>
        /// <value>The block.</value>
        [NotNull]
        public new IGraphicBlock Block
        {
            get { return (IGraphicBlock)base.Block; }
        }

        /// <summary>
        ///     The image to display.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [CanBeNull]
        [UsedImplicitly]
        public Image Image
        {
            get { return _image; }
            private set { OnPropertyChanged("Image", ref _image, value); }
        }

        /// <summary>
        ///     The scaling to apply to the tiles.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [UsedImplicitly]
        public Rect Viewport
        {
            get { return _viewport; }
            private set { OnPropertyChanged("Viewport", ref _viewport, value); }
        }

        /// <summary>
        ///     The way in which to scale the tiles.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [UsedImplicitly]
        public BrushMappingMode ViewportUnits
        {
            get { return _viewportUnits; }
            private set { OnPropertyChanged("ViewportUnits", ref _viewportUnits, value); }
        }

        /// <summary>
        ///     The tiling mode to apply to the image.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [UsedImplicitly]
        public TileMode TileMode
        {
            get { return _tileMode; }
            private set { OnPropertyChanged("TileMode", ref _tileMode, value); }
        }

        /// <summary>
        ///     Gets the way in which the image should be stretched
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [UsedImplicitly]
        public Stretch Stretch
        {
            get { return _stretch; }
            private set { OnPropertyChanged("Stretch", ref _stretch, value); }
        }

        /// <summary>
        ///     Called when the mode changes.
        /// </summary>
        protected override void OnModeChanged()
        {
            base.OnModeChanged();

            SubscribeToBlockCallbacks();
        }

        /// <summary>
        ///     Subscribes to the callbacks necessary to detect changes to the state.
        /// </summary>
        protected override void UnsubscribeFromBlockCallbacks()
        {
            Block.PictureSource.PropertyChanged -= ImagePropertyChanged;
            Block.Visualization.PropertyChanged -= ImagePropertyChanged;

            base.UnsubscribeFromBlockCallbacks();
        }

        /// <summary>
        ///     ICalled whenever a property that impacts the image changes.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void ImagePropertyChanged([CanBeNull] object sender, [NotNull] PropertyChangedEventArgs e)
        {
            try
            {
                switch(e.PropertyName)
                {
                    case "ActiveImage":
                    case "Origin":
                    case "Tiling":
                    case "Visible":
                    case "Scaling":
                        RequestImageRecalculation();
                        break;
                }
            }
            catch(Exception ex)
            {
                ErrorManager.OnUnhandledException(ex);
            }
        }

        /// <summary>
        ///     Request that the image properties are recalculated.
        /// </summary>
        private void RequestImageRecalculation()
        {
            if(!Block.Game.IsPlaying)
                return;

            if(_recalculationQueueing)
                return;

            lock(_recalculationQueueLock)
            {
                if(_recalculationQueueing)
                    return;

                _recalculationQueueing = true;
            }

            if(!Block.Game.IsPlaying)
                return;

            ThreadPool.QueueUserWorkItem(x =>
                                         {
                                             Thread.Sleep(25); // Wait 25 milliseconds in case anyone else has changes to process.

                                             if(!Block.Game.IsPlaying)
                                                 return;

                                             Dispatcher.Invoke(RecalculateImage);
                                         });
        }

        /// <summary>
        ///     Recalculates the properties associated with the image.
        /// </summary>
        private void RecalculateImage()
        {
            if(!Block.Game.IsPlaying)
                return;

            if(!_recalculationQueueing)
                return;

            lock(_recalculationQueueLock)
            {
                if(!_recalculationQueueing)
                    return;

                _recalculationQueueing = false;
            }

            Image = Block.PictureSource.ActiveImage;
            switch(Block.Visualization.Scaling)
            {
                case PictureScalingKind.Original:
                    Stretch = Stretch.None;
                    Viewport = new Rect(0, 0, 1, 1);
                    break;

                case PictureScalingKind.Stretch:
                    Stretch = Stretch.Uniform;
                    Viewport = new Rect(0, 0, ((double)_image.Size.Width / Block.Size.Width), ((double)_image.Size.Height / Block.Size.Height));
                    break;

                case PictureScalingKind.Tile:
                    Stretch = Stretch.None;
                    // TODO: Align the start co-ordinate so that adjacent patterns merge nicely
                    Viewport = new Rect(0, 0, ((double)_image.Size.Width / Block.Size.Width), ((double)_image.Size.Height / Block.Size.Height));
                    break;
            }

            switch(Block.Visualization.Tiling)
            {
                case TileKind.None:
                    TileMode = TileMode.None;
                    ViewportUnits = BrushMappingMode.RelativeToBoundingBox;
                    break;
                case TileKind.HorizontalRepeat:
                    TileMode = TileMode.Tile;
                    ViewportUnits = BrushMappingMode.RelativeToBoundingBox;
                    break;
                case TileKind.VerticalRepeat:
                    ViewportUnits = BrushMappingMode.RelativeToBoundingBox;
                    TileMode = TileMode.Tile;
                    break;
                case TileKind.HorizontalAndVerticalRepeat:
                    ViewportUnits = BrushMappingMode.RelativeToBoundingBox;
                    TileMode = TileMode.Tile;
                    break;
            }

            if(Block.Visualization.Visible)
                Visibility = Visibility.Visible;
            else
                Visibility = Visibility.Collapsed;
        }

        /// <summary>
        ///     Subscribes to the callbacks necessary to detect changes to the state.
        /// </summary>
        protected override void SubscribeToBlockCallbacks()
        {
            Block.PictureSource.PropertyChanged += ImagePropertyChanged;
            Block.Visualization.PropertyChanged += ImagePropertyChanged;

            base.SubscribeToBlockCallbacks();
        }
    }
}